package no.rmz.chez.movementGeneration;

import no.rmz.chez.movementGeneration.MoveGenerator;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
import no.rmz.chez.chess.board.ChessBoard;
import no.rmz.chez.chess.board.Color;
import no.rmz.chez.evaluators.BoardEvaluator;
import no.rmz.chez.game.reporters.MinimaxPlayerReporter;
import no.rmz.chez.game.Move;
import no.rmz.chez.game.MovementGenerator;
import no.rmz.chez.game.reporters.QuietMinimaxPlayerReporter;
import no.rmz.chez.game.moveSelection.Selector;
import no.rmz.chez.misc.Timer;
import no.rmz.genetics.Gene;

public final class MinimaxMoveGenerator implements MoveGenerator {

    /**
     * A reporter object used to report on the
     * progress of this minimax player.
     */
    static final MinimaxPlayerReporter REPORTER = new QuietMinimaxPlayerReporter();
    /**
     * True iff we allow moves to be generated even when
     * there are no kings on the board.  This is useful
     * e.g. for test purposes, but serves no purpose during
     * regular play.
     */
    private boolean disableTerminalCheck = false;
  

    

    public MinimaxMoveGenerator(int d, BoardEvaluator e) {
        assert (d > 0);
        this.maxDepth = d;
        this.evaluator = e;
    }

    /**
     * Disable check for terminal positions. Useful
     * for testing, useless when playing real games.
     *
     */
    public final void doDisableTerminalCheck() {
        disableTerminalCheck = true;
    }
    
    private final int maxDepth;
    /**
     * Determine if we should use AlphaBeta pruning
     * or not.
     */
    private boolean useAlphaBeta = false;
    /**
     * A selector that will select the move used
     * from all the moved found to be equally good by
     * the minimax method.
     */
    private Selector<Move> selector = no.rmz.chez.game.moveSelection.MoveSelectors.FIRST_SELECTOR;

    /**
     * Set the movement selector.
     * @param inSelector
     */
    @Gene
    public void setMovementSelector(final Selector<Move> inSelector) {
        selector = inSelector;
    }

    /**
     * Return the movement selector.
     * @return the movement selector.
     */
    @Gene
    public Selector<Move> getMovementSelector() {
        return selector;
    }

    /**
     * Getting the next move using a minimax tactic.
     * @param b the chessboard to start moving from.
     * @return The best move minimaxing could find.
     */
    @Override
    public final synchronized Move getNextMove(final Color co, final ChessBoard b) {
        initializePossibilities();
        noOfEvaluations = 0;
        Timer timer = new Timer();
        timer.start();
        double goodness;
        if (useAlphaBeta) {
            goodness = miniMaxAlphaBeta(co, b, maxDepth, true, BoardEvaluator.POS_INFTY, BoardEvaluator.NEG_INFTY);
        } else {
            goodness = miniMax(co, b, maxDepth, true);
        }
        timer.stop();
        long millis = timer.getDuration();
        // Extracting the result from the minimax algorithm
        // (a bit cumbersome, but that�s the way it is done.
        final Move result = getMovementSelector().selectFrom(possibilities);
        REPORTER.reportGetNextMoveStatistics(millis, goodness, result, opportunities.keySet(), possibilities, capturingMovesConsidered, discardedMaximizingMoves, noOfEvaluations, maxDepth);
        return result;
    }
    /**
     * All the top level moves that could perform a capture, and their
     * evaluations.
     */
    private Map<Move, Double> opportunities = null;
    /**
     * Capturing moves considered during the top level evaluations.
     */
    private Map<Move, Double> capturingMovesConsidered = new HashMap<>();
    /**
     * The number of evaluations that were performed
     * to find the move we're selecting now.
     */
    private long noOfEvaluations = 0;
    /**
     * The evaluator used by this player.
     */
    private final BoardEvaluator evaluator;
    /**
     * The final maximizing move found when searching.
     */
    private Collection<Move> possibilities = null;
    /**
     * A map the moves discarded by the top level evaluation.
     */
    private final  Map<Double, Collection<Move>> discardedMaximizingMoves = new TreeMap<>();

    /**
     * Set the maximizing moves set to be a new emty
     * collection.
     *
     */
    private void initializePossibilities() {
        possibilities = new LinkedList<>();
    }

    private Collection<Move> getMaximizingMoves(final Color co, final ChessBoard b) {
        return MovementGenerator.getPossibleMoves(co, b);
    }

    /**
     * Generate moves for this player's oposition. These are
     * moves that will be minimized.
     * @param b the board.
     * @return a set of legal moves for this player�s oposition.
     */
    private Collection<Move> getMinimizingMoves(final Color co, final ChessBoard b) {
        return MovementGenerator.getPossibleMoves(Color.oposingColor(co), b);
    }

    /**
     * The minimax algorithm with alhpa/beta pruning, instrumented to report
     * its final maximizing move.
     * <pre>
     * function minimax(node, depth)
    return alphabeta(node, depth, -inf, +inf)
    function alphabeta(node, depth, alpha, beta)
    if node is a terminal node or depth = 0
    return the heuristic value of node
    foreach child of node
    alpha := max(alpha, -alphabeta(child, depth-1, -beta, -beta))
    if alpha >= beta
    return beta
    return alpha
    </pre>
     * @param b the chessboard
     * @param depth the level we are working at, must be 1 or more to generate
     *        moves before trying to select move.
     * @param doMaximize Is this a maximizing move we�re finding?.
     * @param alpha  Current alpha value (alpha/beta pruning)
     * @param beta Current beta value (alpha/beta pruning)
     * @return the integer evaluation maximizing or
     *         minimizing the options the player has.
     */
    private synchronized double miniMaxAlphaBeta(Color co, final ChessBoard b, final int depth, final boolean doMaximize, final double alpha, final double beta) {
        // XXX Not implemented yet
        return 0;
    }

    /**
     * The minimax algorithm, instrumented to report
     * its final maximizing move.
     *
     * metacode for minimax (from wikipedia).
    <pre>
    function minimax(node, depth)
    if node is a terminal node or depth = 0
    return the heuristic value of node
    if the adversary is to play at node
    let a := +infty
    foreach child of node
    a := min(a, minimax(child, depth-1))
    else {we are to play at node}
    let a := -infty
    foreach child of node
    a := max(a, minimax(child, depth-1))
    return a
    </pre>
     * @param b the chessboard
     * @param depth the level we are working at, must be 1 or more to generate
     *        moves before trying to select move.
     * @param doMaximize Is this a maximizing move we�re finding?.
     * @return the integer evaluation maximizing or
     *         minimizing the options the player has.
     */
    private synchronized double miniMax(
            final Color co,
            final ChessBoard b,
            final int depth,
            final boolean doMaximize) {
        if (!disableTerminalCheck && evaluator.isTerminal(b)) {
            // If we are maximizing, then the previous minimax was
            // a minimizing one, that means that if a capture of the
            // king did happen, then it was our king that was captured.
            // This means that if we are maximizing, then a terminal
            // position means that the oposition will win, hence
            // the value we return is negative infinity.
            if (doMaximize) {
                return BoardEvaluator.NEG_INFTY;
            } else {
                return BoardEvaluator.POS_INFTY;
            }
        } else if (depth == 0) {
            // Count number evaluations to measure performance.
            noOfEvaluations += 1;
            // We are always evaluating with our color in mind.
            // the minimaxing will figure out who should benefit.
            double result = evaluator.evaluate(b, co);
            return result;
        } else if (doMaximize) {
            // Find maximizing move
            double a = BoardEvaluator.NEG_INFTY;
            final Collection<Move> moves = getMaximizingMoves(co, b);
            if (depth == maxDepth) {
                opportunities = new HashMap<>();
            }
            for (Move m : moves) {
                final ChessBoard bam = b.afterMove(m);
                final double c = miniMax(co, bam, depth - 1, !doMaximize);
                if (depth == maxDepth && m.isCapturing()) {
                    capturingMovesConsidered.put(m, c);
                }
                if (a < c) {
                    // If this is the top level iteration through moves,
                    // then we shuld collect all the maximizing moves.
                    if (depth == maxDepth) {
                        if (!possibilities.isEmpty()) {
                            discardedMaximizingMoves.put(a, possibilities);
                        }
                        initializePossibilities();
                        possibilities.add(m);
                    }
                    a = c;
                } else if (a == c && depth == maxDepth) {
                    possibilities.add(m);
                }
            }
            return a;
        } else {
            // Find minimizing move
            double a = BoardEvaluator.POS_INFTY;
            for (Move m : getMinimizingMoves(co, b)) {
                ChessBoard bam = b.afterMove(m);
                double c = miniMax(co, bam, depth - 1, !doMaximize);
                if (c < a) {
                    a = c;
                }
            }
            return a;
        }
    }

   
}
